/*
* Copyright (c) 2011 adVolition
*
* Permission is hereby granted, free of charge, to any person
* obtaining a clone of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* clone, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* Except as contained in this notice, the name(s) of the above
* copyright holders shall not be used in advertising or otherwise to
* promote the sale, use or other dealings in this Software without prior
* written authorization.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
 */

package ArenaSimulator;
import java.awt.Graphics2D;
import java.awt.Color;
import com.thoughtworks.xstream.annotations.*;
import java.util.ArrayList;

/**
 * The Simulator class is used to apply the physical and neural mechanics to the
 * robots and arenas.  Each simulator can contain multiple robots and arenas which
 * are randomly paired during the evolution process.
 */
@XStreamAlias("simulator") public class Simulator {

    @XStreamAlias("time") @XStreamAsAttribute private Double _maxArenaTime;
    @XStreamAlias("stall") @XStreamAsAttribute private Double _maxStallTime;
    @XStreamAlias("repeats") @XStreamAsAttribute private int _repeats;

    @XStreamAlias("challenges") private ArrayList<Challenge> _challenges;
    @XStreamAlias("clan") private Clan _clan;

    private transient Robot _currentRobot;
    private transient Challenge _currentChallenge;

    private transient Double _challengeTime = 0.0;
    //private transient int _cuedRobots = 0;
    private transient int _repeatCount = 0; // The number of times the current robot has been tested.
    private transient int _testCount = 0; // The number of robots tested so far.
    private transient int _challengeCount = 0;


    /**
     * Constructs a new simulator to which robots and arenas can be added.
     * Robots are scored based on the time it takes for them
     * to reach the goal in a randomly selected arena.  The quicker the get there, the better.
     */
    public Simulator() {
        configureObject();
    }

    public Object readResolve() {
        configureObject();
        return this;
    }

    /** Used by class constructor and readResolve routines to ensure that newly created
     * objects are configured properly.
     * @return 
     **/
    private void configureObject() {
        if (_maxStallTime == null) _maxStallTime = 10.0;
        if (_maxArenaTime == null) _maxArenaTime = 300.0;
        if (_repeats == 0) _repeats = 0;
        _repeatCount = 0;
        _challengeCount = 0;

        if (_challenges == null) {
            _challenges = new ArrayList<Challenge>();
            _challenges.add(new Challenge());
            _challenges.add(new Challenge());
            _challenges.add(new Challenge());
        }
       
        _challengeTime = 0.0;
        
        if (_clan == null) _clan = new Clan();

        cueRobot();
        cueChallenge();  
    }

    /**
     * Displays numerical data related to the simulation.
     * @param g2d
     */
    private void plotData (Graphics2D g2d) {
        int xOffset = 10; // Offset from window edge.
        int yOffset = 10; // Offset from window edge.
        int lineSpacing = 20; // Distance between lines of text.
        g2d.setColor(Color.blue);
        //g2d.drawString(String.format("Simulator Time: %1$.1f s", _cuedRobots), xOffset, yOffset);
        yOffset += lineSpacing;
        g2d.drawString(String.format("Arena Time: %1$.1f s", _challengeTime), xOffset, yOffset);
        yOffset += lineSpacing;
        g2d.drawString(String.format("Stall Time: %1$.1f s", _currentRobot.getStallTime()), xOffset, yOffset);
        yOffset += lineSpacing;
        g2d.drawString(String.format("Robot Count: %d", _testCount), xOffset, yOffset);
        yOffset += lineSpacing;
        g2d.drawString(String.format("Repeat Count: %d", _repeatCount), xOffset, yOffset);
        yOffset += lineSpacing;
        g2d.drawString(String.format("Challenge Score: %1$.1f", _currentChallenge.getScore() + _currentRobot.getScore()), xOffset, yOffset);
    }



    public void advanceTime(Double timeIncrement) {
        _challengeTime += timeIncrement;
        _currentRobot.computeFutureState(timeIncrement);
        _currentChallenge.checkForCollisions(_currentRobot, _challengeTime);
        //_testRobot.iAmStuck(_testArena);
        _currentRobot.implementFutureState();
        
    }



    /**
     * Plots the arena at x, y using the specified scale.
     * @param x x coordinate for the upper left hand corner of the plot.
     * @param y y coordinate for the upper left hand corner of the plot.
     * @param scale pixels per centimeter.
     * @param g2d pointer to Graphics2D object.
     */
    public void draw(int x, int y, Double scale, Graphics2D g2d) {

        _currentChallenge.draw(x, y, scale, g2d);
        _currentRobot.draw(x, y, scale, g2d);
        
        plotData(g2d);
    }



    /**
     * Runs a portion of the simulation and then pauses so that the arena can
     * be redrawn.  Returns a false when the simulation is complete.
     */
    public Boolean advanceSimulation() {
        
        // Advance arena time if a robot is in the midst of a simulation.
        if (_currentRobot.getStallTime() < _maxStallTime && !_currentChallenge.timeIsUp(_challengeTime)) {          
            advanceTime(0.001);  
            return true;
        } else {
        
            // Otherwise, the robot has stalled or time is up.  Score the robot.
            _currentRobot.addScore(_currentChallenge.getScore());
            _testCount ++;

            _currentRobot.reset();
            _currentChallenge.reset();
            // Does robot need to repeat the challenge?

            if (_repeatCount > _repeats) {

                // If not, reset the repeat count and cue another challenge.
                _repeatCount = 0;

                // Are there any more challenges for the robot to run?
                if (_challengeCount > _challenges.size()) {

                    // If not, reset the challenge count and grab a new robot.
                    _challengeCount = 0;
                    _clan.nominate(_currentRobot);  // See if robot should be added to the clan.
                    cueRobot(); // Grab a new robot to test.       
                } else {
                    _challengeCount ++;
                    cueChallenge();
                }

            } else {
                _repeatCount ++;
                _currentRobot.reset();
            }
            
            repositionRobot();
        }
    
        return true;
    }



    /**
     * Gets a child robot from the clan.
     */
   private void cueRobot() {
       _currentRobot = _clan.getChild();
   }
   
   /**
    * Returns the robot to it's position in the center of the arena.
    */
   private void repositionRobot() {
       _currentRobot.setPosition(50.0, 50.0);
       _currentRobot.setCurrentFacing(Math.random() * 360.0);
       _challengeTime = 0.0;
   }

   private void cueChallenge() {
       _currentChallenge = _challenges.get((int)(Math.random() * _challenges.size()));
       _currentChallenge.reset();
   }
}


